﻿using System;

using AnimatingHair.Entity;
using OpenTK.Graphics.OpenGL;
using System.Drawing;
using OpenTK;
using System.Collections.Generic;
using System.IO;

namespace AnimatingHair.Rendering
{
    class HairRenderer
    {
        const string vertexShaderLocation = @"../../_Data/Shaders/billboardVS.glsl";
        const string fragmentShaderLocation = @"../../_Data/Shaders/hairFS.glsl";

        //const string vertexShaderLocation = @"Shaders/billboardVS.glsl";
        //const string fragmentShaderLocation = @"Shaders/hairFS.glsl";

        private readonly Hair hair;
        private readonly Camera camera;
        private int splatTexture, axisLoc, eyeLoc, sign1Loc, sign2Loc, shaderProgram, vertexShaderObject, fragmentShaderObject;

        public HairRenderer( Camera camera, Hair hair )
        {
            this.hair = hair;
            this.camera = camera;

            splatTexture = Utility.UploadTexture( "vlas.png" );

            using ( StreamReader vs = new StreamReader( vertexShaderLocation ) )
            {
                using ( StreamReader fs = new StreamReader( fragmentShaderLocation ) )
                    createShaders( vs.ReadToEnd(), fs.ReadToEnd(),
                        out vertexShaderObject, out fragmentShaderObject,
                        out shaderProgram );
            }

            getShaderLocations();
        }

        public void Render()
        {
            renderWithVS();
            //renderWithoutVS();
        }

        private void renderWithoutVS()
        {
            List<HairParticle> sorted = new List<HairParticle>();

            foreach ( HairParticle hp in hair.Particles )
            {
                sorted.Add( hp );
                hp.DistanceFromCamera = (hp.Position - camera.Eye).Length;
            }

            sorted.Sort(
                ( hp1, hp2 )
                =>
                hp2.DistanceFromCamera.CompareTo( hp1.DistanceFromCamera )
                );

            GL.BindTexture( TextureTarget.Texture2D, splatTexture );

            for ( int i = 0; i < sorted.Count; i++ )
            {
                renderParticle( sorted[ i ] );
            }
        }

        private void renderWithVS()
        {
            GL.UseProgram( shaderProgram );

            List<HairParticle> sorted = new List<HairParticle>();

            foreach ( HairParticle hp in hair.Particles )
            {
                sorted.Add( hp );
                hp.DistanceFromCamera = (hp.Position - camera.Eye).Length;
            }

            sorted.Sort(
                ( hp1, hp2 )
                =>
                hp2.DistanceFromCamera.CompareTo( hp1.DistanceFromCamera )
                );

            GL.BindTexture( TextureTarget.Texture2D, splatTexture );

            GL.Uniform3( eyeLoc, (Vector3)camera.Eye );

            for ( int i = 0; i < sorted.Count; i++ )
            {
                if ( !sorted[ i ].IsRoot )
                    renderParticle2( sorted[ i ] );
            }

            GL.UseProgram( 0 );
        }

        private void renderParticle( HairParticle hp )
        {
            Vector3d objPos = hp.Position;
            Vector3d look = camera.Eye - objPos;
            //look.Normalize();

            Vector3d up = hp.Direction;
            Vector3d right = Vector3d.Cross( up, look );

            right.Normalize();
            up.Normalize();
            Vector3d.Cross( right, up );

            float renderSizeHorizontal = 0.25f;
            float renderSizeVertical = 0.75f;

            right = -right;

            GL.Begin( BeginMode.Quads );
            {
                //if ( hp.IsRoot )
                //    GL.Color4( Color.FromArgb( 127, 0, 0, 0 ) );
                //else
                //    GL.Color4( Color.FromArgb( 127, 180, 120, 50 ) );

                //GL.Color4( Color.FromArgb( 255, 0, 0, 0 ) );
                GL.TexCoord2( 0, 1 ); GL.Vertex3( (Vector3)(objPos + renderSizeHorizontal * right - renderSizeVertical * up) );
                GL.TexCoord2( 0, 0 ); GL.Vertex3( (Vector3)(objPos + renderSizeHorizontal * right + renderSizeVertical * up) );
                GL.TexCoord2( 1, 0 ); GL.Vertex3( (Vector3)(objPos - renderSizeHorizontal * right + renderSizeVertical * up) );
                GL.TexCoord2( 1, 1 ); GL.Vertex3( (Vector3)(objPos - renderSizeHorizontal * right - renderSizeVertical * up) );
            }
            GL.End();
        }

        private void renderParticle2( HairParticle hp )
        {
            GL.Uniform3( axisLoc, (Vector3)hp.Direction );

            //GL.VertexAttribI1( typeLoc, 0 );
            //GL.Translate( (Vector3)hp.Position );

            GL.Begin( BeginMode.Quads );
            {
                GL.TexCoord2( 0, 1 );
                GL.VertexAttrib1( sign1Loc, 1 );
                GL.VertexAttrib1( sign2Loc, -1 );
                GL.Vertex3( (Vector3)hp.Position );

                GL.TexCoord2( 0, 0 );
                GL.VertexAttrib1( sign1Loc, 1 );
                GL.VertexAttrib1( sign2Loc, 1 );
                GL.Vertex3( (Vector3)hp.Position );

                GL.TexCoord2( 1, 0 );
                GL.VertexAttrib1( sign1Loc, -1 );
                GL.VertexAttrib1( sign2Loc, 1 );
                GL.Vertex3( (Vector3)hp.Position );

                GL.TexCoord2( 1, 1 );
                GL.VertexAttrib1( sign1Loc, -1 );
                GL.VertexAttrib1( sign2Loc, -1 );
                GL.Vertex3( (Vector3)hp.Position );
            }
            GL.End();
        }

        #region Private auxiliary methods

        private void getShaderLocations()
        {
            axisLoc = GL.GetUniformLocation( shaderProgram, "axis" );
            sign1Loc = GL.GetAttribLocation( shaderProgram, "sign1" );
            sign2Loc = GL.GetAttribLocation( shaderProgram, "sign2" );
            eyeLoc = GL.GetAttribLocation( shaderProgram, "eye" );
        }

        // Creates, compiles and links a vertex shader.
        // Fragment shader is not needed for now; it's commented out.
        private static void createShaders(
            string vs, string fs,
            out int vertexObject, out int fragmentObject,
            out int program )
        {
            int statusCode;
            string info;

            vertexObject = GL.CreateShader( ShaderType.VertexShader );
            fragmentObject = GL.CreateShader( ShaderType.FragmentShader );

            // Compile vertex shader
            GL.ShaderSource( vertexObject, vs );
            GL.CompileShader( vertexObject );
            GL.GetShaderInfoLog( vertexObject, out info );
            GL.GetShader( vertexObject, ShaderParameter.CompileStatus, out statusCode );

            if ( statusCode != 1 )
                throw new ApplicationException( info );

            // Compile fragment shader
            GL.ShaderSource( fragmentObject, fs );
            GL.CompileShader( fragmentObject );
            GL.GetShaderInfoLog( fragmentObject, out info );
            GL.GetShader( fragmentObject, ShaderParameter.CompileStatus, out statusCode );

            if ( statusCode != 1 )
                throw new ApplicationException( info );

            program = GL.CreateProgram();
            GL.AttachShader( program, fragmentObject );
            GL.AttachShader( program, vertexObject );

            GL.LinkProgram( program );
        }

        #endregion
    }
}
